<html>
  <head>
   <meta charset="utf-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1">
   <meta name="theme-color" content="#008000"/>
   <title>imagetracking_ml</title>
   <link rel="manifest" href="imagetracking_ml_manifest.json">
   <script>
    if('serviceWorker' in navigator) {
      navigator.serviceWorker.register('/imagetracking_ml_sw.js')
        .then(function() {
              console.log('Service Worker Registered');
        });
    }
   </script>
    <style>
      body {
        margin: 0;
      }
    </style>
  </head>
  <body>
  <h1>imagetracking_ml</h1>
  <script src="three.js"></script>
  <script src="inflate.min.js"></script>
  <script src="FBXLoader.js"></script>
  <script src="sprite3d.js"></script>
  <script>
    let container, scene, camera, renderer, session;
    let swordMesh = null, /* model = null, */imageTracker = null, imageMeshes = [];

    let scaleFactor = 1;

    const _requestSpriteMesh = url => new Promise((accept, reject) => {
      const img = new Image();
      img.crossOrigin = 'Anonymous';
      img.src = url;
      img.onload = () => {
        const spriteMesh = sprite3d.makeSpriteMesh(img);
        accept(spriteMesh);
      };
      img.onerror = err => {
        reject(err);
      };
    });
    /* const _makeLeaperMesh = () => {
      const mesh = model.clone();
      mesh.startTime = Date.now();
      // mesh.endTime = mesh.startTime + 10000;
      mesh.size = 1;
      return mesh;
    }; */
    const _makeImageMesh = (() => {
      const geometry = new THREE.BoxBufferGeometry(scaleFactor, scaleFactor, scaleFactor);
      const material = new THREE.MeshPhongMaterial({
        color: 0x9ccc65,
      });
      return () => {
        const mesh = new THREE.Mesh(geometry, material);
        mesh.frustumCulled = false;
        mesh.startTime = Date.now();
        // mesh.endTime = mesh.startTime + 10000;
        mesh.size = 1;
        return mesh;
      };
    })();

    function init() {
      container = document.createElement('div');
      document.body.appendChild(container);

      scene = new THREE.Scene();
      scene.matrixAutoUpdate = false;
      // scene.background = new THREE.Color(0x3B3961);

      camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
      // camera.position.set(0, 1, 0);
      // camera.lookAt(new THREE.Vector3());
      scene.add(camera);

      // const ambientLight = new THREE.AmbientLight(0x333333);
      const ambientLight = new THREE.AmbientLight(0x808080);
      scene.add(ambientLight);

      const directionalLight = new THREE.DirectionalLight(0x808080, 1);
      directionalLight.position.set(1, 1, 1);
      scene.add(directionalLight);

      swordMesh = new THREE.Object3D();

      _requestSpriteMesh('sword.png')
        .then(newSwordMesh => {
          newSwordMesh.position.set(0.05, 0.11, -0.15);
          newSwordMesh.quaternion.setFromUnitVectors(
            new THREE.Vector3(1, 1, 0).normalize(),
            new THREE.Vector3(0, 1, 0),
          ).premultiply(
            new THREE.Quaternion().setFromUnitVectors(
              new THREE.Vector3(0, 1, 0),
              new THREE.Vector3(0, 1, -1).normalize(),
             )
          );
          newSwordMesh.frustumCulled = false;

          swordMesh.add(newSwordMesh);
          scene.add(swordMesh);
        })
        .catch(err => {
          console.warn(err.stack);
        });

      /* const loader = new THREE.FBXLoader();
      // loader.setResourcePath('models/');
      loader.load('models/OutsidePortal_Leaper_Notification.fbx', object => {
        const box = new THREE.Box3().setFromObject(object);
        const size = box.getSize(new THREE.Vector3());
        // console.log('got size', size.x);
        scaleFactor = size.x;

        // object.scale.multiplyScalar(0.01);
        // object.matrix.compose(object.position, object.quaternion, object.scale);
        // object.updateMatrixWorld(true);
        // object.frustumCulled = false;

        object.traverse(child => {
          if (child.material) {
            child.material.color.setHex(0xFFFFFF);
          }
          child.frustumCulled = false;
        });

        model = object;
      }, progress => {
      }, error => {
        console.warn(err);
      }); */

      let lastImageMeshTime = 0;
      const _onTrack = update => {
        if (update) {
          if (imageMeshes.length < 10) {
            const now = Date.now();
            if ((now - lastImageMeshTime) >= 2000) {
              // const mesh = _makeLeaperMesh();
              const mesh = _makeImageMesh();

              mesh.position.fromArray(update.position);
              mesh.quaternion.fromArray(update.rotation);
              mesh.scale.set(update.size/scaleFactor, update.size/scaleFactor, update.size/scaleFactor);
              mesh.size = update.size;
              mesh.updateMatrixWorld();

              scene.add(mesh);

              imageMeshes.push(mesh);

              lastImageMeshTime = now;
            }
          }
        } else {
          // model.visible = false;
        }
      };
      if (window.browser && window.browser.magicleap) {
        const img = new Image();
        img.onload = () => {
          console.log('tracker image loaded', img.width, img.height);

          // imageTracker = window.browser.magicleap.RequestImageTracking(img, 0.027);
          // imageTracker = window.browser.magicleap.RequestImageTracking(img, 0.11);
          imageTracker = window.browser.magicleap.RequestImageTracking(img, 0.09);
          imageTracker.ontrack = _onTrack;
        };
        img.onerror = err => {
          console.warn(err.stack);
        };
        // img.src = 'leaper.png';
        img.src = 'tracker.png';
      }

      renderer = new THREE.WebGLRenderer({
        antialias: true,
        alpha: true,
      });
      renderer.setPixelRatio(window.devicePixelRatio);
      renderer.setSize(window.innerWidth, window.innerHeight);

      // window.browser.magicleap.RequestDepthPopulation(true);
      // renderer.autoClearDepth = false;

      container.appendChild(renderer.domElement);

      renderer.setAnimationLoop(animate);
    }

    const localVector = new THREE.Vector3();
    const localVector2 = new THREE.Vector3();
    const localQuaternion = new THREE.Quaternion();
    const localLine = new THREE.Line3();
    let lastTime = Date.now();
    function animate(time, frame) {
      if (renderer.vr.enabled) {
        const inputSources = session.getInputSources();
        const inputSource = inputSources[0];
        const pose = frame.getInputPose(inputSource);

        swordMesh.matrix.fromArray(pose.targetRay.transformMatrix);
        swordMesh.matrix.decompose(swordMesh.position, swordMesh.quaternion, swordMesh.scale);
        swordMesh.updateMatrixWorld(true);
      }

      localLine.start.copy(swordMesh.position);
      localLine.end.copy(localLine.start).add(
        localVector.set(0, 1, -1).normalize().multiplyScalar(0.5)
          .applyQuaternion(swordMesh.quaternion)
      );

      const now = Date.now();
      const timeDiff = now - lastTime;
      const eraseList = [];
      for (let i = 0; i < imageMeshes.length; i++) {
        const mesh = imageMeshes[i];
        // if (now < mesh.endTime) {
          const distance = localLine.closestPointToPoint(mesh.position, true, localVector)
            .distanceTo(mesh.position);

          if (distance > 0.1) {
            const vrCamera = renderer.vr.enabled ? renderer.vr.getCamera(camera).cameras[0] : camera;
            vrCamera.matrixWorld.decompose(vrCamera.position, vrCamera.quaternion, vrCamera.scale);
            mesh.quaternion.slerp(
              localQuaternion.setFromUnitVectors(
                localVector.set(0, 0, 1),
                localVector2.copy(vrCamera.position).sub(mesh.position).normalize()
              ),
              2*timeDiff/1000
            );
            mesh.position.add(
              localVector.set(0, 0, 0.3 * timeDiff/1000 * Math.min((now - mesh.startTime)/2000, 1))
                .applyQuaternion(mesh.quaternion)
            );
            mesh.scale.z = Math.min((now - mesh.startTime)/2000, 1) * mesh.size/scaleFactor;
            mesh.updateMatrixWorld(true);
          } else {
            scene.remove(mesh);
            eraseList.push(mesh);
          }
        /* } else {
          scene.remove(mesh);
          eraseList.push(mesh);
        } */
      }
      for (let i = 0; i < eraseList.length; i++) {
        imageMeshes.splice(imageMeshes.indexOf(eraseList[i]), 1);
      }
      lastTime = now;

      renderer.render(scene, renderer.vr.enabled ? renderer.vr.getCamera(camera) : camera);
    }

    init();

    (async () => {
      console.log('request session');
      session = await navigator.xr.requestSession({
        exclusive: true,
      }).catch(err => Promise.resolve(null));

      if (session) {
        // console.log('request first frame');
        session.requestAnimationFrame((timestamp, frame) => {
          renderer.vr.setSession(session, {
            frameOfReferenceType: 'stage',
          });

          const {views} = frame.getViewerPose();
          const viewport = session.baseLayer.getViewport(views[0]);
          const width = viewport.width;
          const height = viewport.height;

          renderer.setSize(width * 2, height);

          renderer.setAnimationLoop(null);

          renderer.vr.enabled = true;
          renderer.vr.setAnimationLoop(animate);

          console.log('running!');
        });
      } else {
        console.log('no xr devices');
      }
    })();
  </script>
  </body>
</html>
